package com.gitee.wsl.mathematics.projection.polygon

import com.gitee.wsl.ext.number.DoubleApproxComparator
import com.gitee.wsl.mathematics.coordinate.d3.Coordinate3D
import com.gitee.wsl.mathematics.geometry.d3.face.Face
import com.gitee.wsl.mathematics.geometry.d3.plane.Plane
import com.gitee.wsl.mathematics.geometry.d3.plane.dualPlane
import com.gitee.wsl.mathematics.geometry.d3.plane.dualPoint
import com.gitee.wsl.mathematics.geometry.d3.plane.tangentPoint
import com.gitee.wsl.mathematics.geometry.d3.vertex.Vertex
import com.gitee.wsl.mathematics.vector.ext.cross
import com.gitee.wsl.mathematics.vector.ext.approx
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.ext.minus
import com.gitee.wsl.mathematics.vector.vec3.Vec3

class PolygonProjection(
    val vs: List<Coordinate3D<Vec3>>
) : Comparable<PolygonProjection> {
    override fun compareTo(other: PolygonProjection): Int =
        VertexListApproxComparator.compare(vs, other.vs)

    companion object {
        val Empty = PolygonProjection(emptyList())
    }
}

infix fun PolygonProjection.approx(other: PolygonProjection) =
    vs.size == other.vs.size &&
            vs.indices.all { i -> vs[i] approx other.vs[i] }

val VertexListApproxComparator : Comparator<List<Coordinate3D<*>>> =
    LexicographicListComparator(Vec3ApproxComparator)

// project face vertices using a given starting index
private fun computeProjectionFigureAt(plane: Plane, vs: List<Coordinate3D<*>>, i: Int): PolygonProjection {
    val v0 = vs[i]
    val c = plane.tangentPoint
    if (v0 approx c) return PolygonProjection.Empty
    val n = vs.size
    val ux = (v0 - c).unit
    val list = ArrayList<Vec3>(n)
    for (j in 0 until n) {
        val v = vs[(i + j) % n] - c
        val x = ux dot v
        val y = (ux cross v) dot plane
        val z = ux dot plane
        list += Vec3(x, y, z)
    }
    return PolygonProjection(list)
}

private fun computeProjectionFigure(plane: Plane, vs: List<Coordinate3D<*>>): PolygonProjection =
    vs.indices.maxOfOrNull { i -> computeProjectionFigureAt(plane, vs, i) }!!

fun Face.computeProjectionFigure() =
    computeProjectionFigure(this, fvs)

fun Face.computeProjectionFigureAt(v: Vertex) =
    computeProjectionFigureAt(this, fvs, fvs.indexOf(v))

// use dual to compute vertex figure
fun Vertex.computeProjectFigure() =
    computeProjectionFigure(
        dualPlane(1.0),
        directedEdges.map { it.r.dualPoint(1.0) }
    )

fun Vertex.computeProjectionFigureAt(f: Face) =
    computeProjectionFigureAt(
        dualPlane(1.0),
        directedEdges.map { it.r.dualPoint(1.0) },
        directedEdges.indexOfFirst { it.r == f }
    )

class LexicographicListComparator<T>(val comparator: Comparator<T>) : Comparator<List<T>> {
    override fun compare(a: List<T>, b: List<T>): Int {
        val n = minOf(a.size, b.size)
        for (i in 0 until n) {
            val c = comparator.compare(a[i], b[i])
            if (c != 0) return c
        }
        if (a.size < b.size) return -1
        if (a.size > b.size) return 1
        return 0
    }
}

object Vec3ApproxComparator : Comparator<Coordinate3D<*>> {
    override fun compare(a: Coordinate3D<*>, b: Coordinate3D<*>): Int {
        val x = DoubleApproxComparator.compare(a.x, b.x)
        if (x != 0) return x
        val y = DoubleApproxComparator.compare(a.y, b.y)
        if (y != 0) return y
        return DoubleApproxComparator.compare(a.z, b.z)
    }
}